﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace AzukiMapUI
{
	[TemplatePart(Name = DragDropControl.ElementRootName, Type = typeof(Canvas))]
	[TemplatePart(Name = DragDropControl.ElementContainerName, Type = typeof(Panel))]
	[TemplatePart(Name = DragDropControl.ElementContentName, Type = typeof(FrameworkElement))]
	public class DragDropControl : ContentControl
	{
        #region Fields & Events

        protected Canvas ElementRoot { get; set; }
        private const string ElementRootName = "Root";

        private Panel ElementContainer { get; set; }
        private const string ElementContainerName = "Container";

        private TranslateTransform ElementTransform;

        private const string ElementContentName = "Content";

        private Point OldPoint;
		private bool MouseDown = false;
		private string SizingDirection = "";

		public event EventHandler PositionChanged;
		public event EventHandler Resized;

        #endregion

        #region Constructor
        public DragDropControl()
		{
            this.DefaultStyleKey = typeof(DragDropControl);
			this.SetValue(Canvas.ZIndexProperty, DragDropControl.MaxZIndex);
			DragDropControl.MaxZIndex++;
            this.ElementTransform = new TranslateTransform();
           
            this.MouseLeftButtonDown += OnMouseLeftButtonDown;
            this.MouseLeftButtonUp += OnMouseLeftButtonUp;
            this.MouseMove += OnMouseMove;            
		}                
		#endregion		

		#region MaxZIndex
		/// <summary>
		/// Identifies the MaxZIndex dependency property.
		/// </summary>		
        public static int MaxZIndex { get; set; }

		#endregion MaxZIndex

		#region CanResize
		/// <summary>
		/// Identifies the CanResize dependency property.
		/// </summary>
		public static readonly DependencyProperty CanResizeProperty = DependencyProperty.Register("CanResize", typeof(bool), typeof(DragDropControl), null);

		/// <summary>
		/// Gets or sets the CanResize possible Value of the bool object.
		/// </summary>
		public bool CanResize
		{
			get { return (bool)GetValue(CanResizeProperty); }
			set { SetValue(CanResizeProperty, value); }
		}
		#endregion CanResize

        #region OffsetX
        /// 
        /// Identifies the OffsetX dependency property.
        /// 
        public static readonly DependencyProperty OffsetXProperty =
            DependencyProperty.Register(
            "OffsetX",
            typeof(double),
            typeof(DragDropControl),
            null);

        /// 
        /// Gets or sets the OffsetX possible Value of the int object.
        /// 
        public double OffsetX
        {
            get { return (double)GetValue(OffsetXProperty); }
            set 
            { 
                SetValue(OffsetXProperty, value);
                this.ElementTransform.X = value;
            }
        }       
        #endregion OffsetX

        #region OffsetY
        /// 
        /// Identifies the OffsetY dependency property.
        /// 
        public static readonly DependencyProperty OffsetYProperty =
            DependencyProperty.Register(
            "OffsetY",
            typeof(double),
            typeof(DragDropControl),
            null);

        /// 
        /// Gets or sets the OffsetY possible Value of the int object.
        /// 
        public double OffsetY
        {
            get { return (double)GetValue(OffsetYProperty); }
            set 
            { 
                SetValue(OffsetYProperty, value); 
                this.ElementTransform.Y = value; 
            }
        }       
        #endregion OffsetY

        #region Overrides

        public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();
            this.ElementRoot = GetTemplateChild(ElementRootName) as Canvas;
            this.ElementContainer = GetTemplateChild(ElementContainerName) as Panel;
            if (this.ElementContainer == null)
                throw new Exception(ElementContainerName + " is Missing in the template");
            this.RenderTransform = ElementTransform;
		}

        protected override Size ArrangeOverride(Size finalSize)
        {
            Size s = base.ArrangeOverride(finalSize);
            if (this.Width.ToString() == "NaN")
                this.Width = this.ElementContainer.ActualWidth;
            if (this.Height.ToString() == "NaN")
                this.Height = this.ElementContainer.ActualHeight;
            return s;
        }

        #endregion

        #region Mouse Events

        void OnMouseMove(object sender, MouseEventArgs e)
		{
			var newPoint = e.GetPosition(null);
			if (this.MouseDown)
			{				               
				if (this.SizingDirection == "")
				{
					// Moving the Panel					                  
                    this.OffsetX += newPoint.X - this.OldPoint.X;
                    this.OffsetY += newPoint.Y - this.OldPoint.Y;                  
					RaisePositionChanged();					
				}
				else
				{
					// Resizing the Panel
                    var p = this.Parent as Panel;
                    
					var change = new Point(newPoint.X - this.OldPoint.X, newPoint.Y - this.OldPoint.Y);
					var c = this.Content as UIElement;
					if (this.SizingDirection.IndexOf("n") > -1 && this.Height - change.Y > 2) //North
					{
                        this.OffsetY += change.Y; 				        
						this.Height -= change.Y;
                        RaisePositionChanged();
                        RaiseResized();
					}
					if (this.SizingDirection.IndexOf("s") > -1 && this.Height + change.Y > 2) //South
					{                      	
						this.Height += change.Y;                   
                        RaiseResized();					
					}
                    if (this.SizingDirection.IndexOf("w") > -1 && this.Width - change.X > 2) //West
                    {
                        this.OffsetX += change.X;
                        this.Width -= change.X;
                        RaisePositionChanged();
                        RaiseResized();
                    }
					if (this.SizingDirection.IndexOf("e") > -1 && this.Width + change.X > 2) //East
					{                      
                        this.Width += change.X;                                              
					}                    
				}
				this.OldPoint = newPoint;
			}
			else
			{
				// Check to see if mouse is on a resize area
				if (this.CanResize)
				{
					this.SizingDirection = ResizeHitTest(newPoint);
					SetCursor(this.SizingDirection);
				}
			}
		}
		
		void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
		{
			if (this.MouseDown)
			{
				this.Opacity = 1;
				this.ReleaseMouseCapture();
				this.MouseDown = false;
			}
		}

		void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			if (e.Handled)
				return;

			this.MouseDown = true;
			this.OldPoint = e.GetPosition(null);
			this.Opacity *= 0.8;
			this.CaptureMouse();
			int zIndex = (int)this.GetValue(Canvas.ZIndexProperty);
			if (zIndex > DragDropControl.MaxZIndex)
				DragDropControl.MaxZIndex = zIndex + 1;
			else if (zIndex < DragDropControl.MaxZIndex)
				DragDropControl.MaxZIndex++;
			this.SetValue(Canvas.ZIndexProperty, DragDropControl.MaxZIndex);
		}

		#endregion

		#region Helper Methods

		string ResizeHitTest(Point pt)
		{
			double x0 = pt.X;
			double y0 = pt.Y;

            Point P = Position.GetAbsolutePosition(ElementContainer);

			double x1 = P.X;
			double y1 = P.Y;
            double x2 = x1 + ElementContainer.ActualWidth;
            double y2 = y1 + ElementContainer.ActualHeight;

			// Corners
			if (Math.Abs(x0 - x1) < 6 && Math.Abs(y0 - y1) < 6)
				this.SizingDirection = "nw";
			else if (Math.Abs(x0 - x1) < 6 && Math.Abs(y2 - y0) < 6)
				this.SizingDirection = "sw";
			else if (Math.Abs(x2 - x0) < 6 && Math.Abs(y2 - y0) < 6)
				this.SizingDirection = "se";
			else if (Math.Abs(x2 - x0) < 6 && Math.Abs(y0 - y1) < 6)
				this.SizingDirection = "ne";
			// Sides
			else if (Math.Abs(y0 - y1) < 4)
				this.SizingDirection = "n";
			else if (Math.Abs(x2 - x0) < 4)
				this.SizingDirection = "e";
			else if (Math.Abs(y2 - y0) < 4)
				this.SizingDirection = "s";
			else if (Math.Abs(x0 - x1) < 4)
				this.SizingDirection = "w";
			else
				this.SizingDirection = "";

			return this.SizingDirection;
		}

		void SetCursor(string resize)
		{
			if (resize == "n" || resize == "s")
				Cursor = Cursors.SizeNS;
			else if (resize == "w" || resize == "e")
				Cursor = Cursors.SizeWE;
			else
				Cursor = Cursors.Arrow;
		}

		void RaiseResized()
		{
			if (this.Resized != null)
				Resized(this, null);
		}

		void RaisePositionChanged()
		{
			if (this.PositionChanged != null)
				PositionChanged(this, null);
		}

		#endregion
	}		
}
